Un stack para supervisar contadores de energía por radio, sin cableado de datos.
Arquitectura completa para leer contadores eléctricos Modbus (Eastron SDM630) a través de LoRaWAN y volcarlos en un panel de control con histórico y alarmas: Contador → Dragino RS485-LN → Gateway LoRaWAN → ChirpStack → Mosquitto → Node-RED → ThingsBoard CE. Pensada para 20-50 contadores en un radio de hasta 5 km, en la región europea EU868, desplegada con docker compose en un único servidor Ubuntu.
Del registro Modbus del contador a un dashboard en el navegador, sin ningún cable de datos entre medias.
Cada contador se conecta a un nodo Dragino que traduce sus lecturas Modbus a radio LoRa. El gateway capta esa radio y la reenvía al servidor, donde cuatro piezas de software encadenadas —ChirpStack, Mosquitto, Node-RED y ThingsBoard— descifran, traducen y visualizan el dato hasta convertirlo en un gráfico en tiempo real.
01-03 · Paneles de acceso
ChirpStack, ThingsBoard y Node-RED, cada uno en su propia pestaña, embebidos con la URL que tú indiques.
04 · Arquitectura
Qué hace cada pieza del stack, en una frase, y cómo se conectan entre sí.
05 · Flujo de un dato
El recorrido completo de una lectura de tensión, paso a paso, desde el contador hasta la pantalla.
06-08 · Despliegue
Puesta en marcha, el codec de decodificación del SDM630, y qué reforzar antes de producción.
Network & Application Server LoRaWAN, embebido aquí.
Autentica gateways y dispositivos, descifra las tramas, ejecuta el codec de cada Device Profile y gestiona applications, gateways y devices. Por defecto apunta a http://localhost:8080, el puerto que publica el docker-compose.yml del stack — cambia la URL si tu instancia vive en otra máquina, dominio o detrás de un reverse proxy.
Si no carga: comprueba que el contenedor está arriba (docker compose ps) y que la URL es alcanzable desde este navegador — "Abrir en pestaña nueva" es el diagnóstico más rápido.
Dashboards, alarmas e histórico, embebidos aquí.
Visualización en tiempo real, gestión de usuarios y clientes, reglas de alarma y almacenamiento de series temporales de cada contador. Por defecto apunta a http://localhost:8081 (el docker-compose.yml mapea el 9090 interno de ThingsBoard a ese puerto del host) — cámbiala si tu instancia vive en otra máquina o dominio.
Si no carga: comprueba que el contenedor está arriba (docker compose ps) y que la URL es alcanzable desde este navegador — "Abrir en pestaña nueva" es el diagnóstico más rápido.
El traductor ChirpStack → ThingsBoard, embebido aquí.
Editor de flujos donde se importa flows-example.json: escucha los uplinks decodificados en Mosquitto y los reempaqueta en la Gateway API de ThingsBoard. Por defecto apunta a http://localhost:1880 — cámbiala si tu instancia vive en otra máquina o dominio.
Si no carga: comprueba que el contenedor está arriba (docker compose ps) y que la URL es alcanzable desde este navegador — "Abrir en pestaña nueva" es el diagnóstico más rápido.
Ocho piezas, cada una con un único trabajo.
El punto que más suele confundir: ChirpStack y ThingsBoard no se hablan directamente. Cada uno tiene su propio "idioma" de MQTT (topics y formato de payload distintos), por eso Node-RED existe en medio — sin él, los datos llegarían a Mosquitto pero ThingsBoard no sabría interpretarlos.
Contador SDM630
Modbus RTU
Dragino RS485-LN
RS485 → LoRaWAN
Gateway LoRaWAN
radio EU868
gateway-bridge
UDP 1700 → MQTT
ChirpStack
Network Server LoRaWAN
Mosquitto
broker MQTT central
Node-RED
traductor de formato
ThingsBoard CE
dashboards y alarmas
ChirpStack lee y escribe su propio estado (claves, sesiones, dispositivos) en PostgreSQL + Redis, en paralelo al flujo de datos — no forma parte de la cadena de mensajes, es su memoria interna.
| Componente | Rol | Analogía |
|---|---|---|
| Contador SDM630 | Mide la energía y expone sus datos por Modbus RTU | El "sensor" |
| Dragino RS485-LN | Traduce Modbus → radio LoRa | El "módem" del contador |
| Gateway LoRaWAN | Antena que capta la radio y la reenvía por IP | La "antena de telefonía" |
| gateway-bridge | Traduce el protocolo UDP del gateway a MQTT | Un adaptador de enchufe |
| ChirpStack | Autentica, descifra y decodifica cada trama (aplica el codec) | El "operador de telefonía" |
| Postgres / Redis | Guardan el estado interno de ChirpStack (claves, sesiones...) | La "base de datos" del operador |
| Mosquitto | Autopista de mensajes MQTT compartida por todos | El "cartero" |
| Node-RED | Reformatea el mensaje de ChirpStack al formato de ThingsBoard | El "traductor" |
| ThingsBoard CE | Dashboards, alarmas, histórico, usuarios/clientes | El panel de control del usuario final |
A diferencia de ChirpStack, ThingsBoard y Node-RED (sección 01-03), estas dos piezas no tienen panel embebible: son infraestructura de transporte, no interfaces de usuario.
De "230 V en la fase L1" a un punto en una gráfica, paso a paso.
El mismo recorrido, pero contado como la vida de un único dato — útil para depurar el stack cuando algo no llega hasta el dashboard.
1 · El contador mide
El contador mide, por ejemplo, 230 V en la fase L1, junto con el resto de registros Modbus configurados (corrientes, potencias, energía acumulada).
2 · El Dragino empaqueta
El Dragino RS485-LN lee ese registro Modbus por su bus RS485 y lo mete en una trama de radio LoRa, junto con las demás lecturas configuradas por AT+COMMAND.
3 · El gateway reenvía
El gateway LoRaWAN capta la señal de radio y la reenvía por Internet/Ethernet al servidor, mediante UDP (protocolo Semtech packet forwarder), al puerto 1700.
4 · El bridge traduce el transporte
chirpstack-gateway-bridge convierte ese paquete UDP en un mensaje MQTT interno, publicado en Mosquitto.
5 · ChirpStack decodifica
ChirpStack descifra la trama, identifica el dispositivo Dragino, ejecuta el codec sdm630_codec.js de su Device Profile y obtiene {"voltaje_l1": 230.4, ...}.
6 · ChirpStack publica
ChirpStack publica ese JSON ya decodificado en Mosquitto, en el topic application/.../event/up.
7 · Node-RED reempaqueta
Node-RED está escuchando ese topic, coge el JSON y lo reempaqueta en el formato que espera ThingsBoard (v1/gateway/telemetry, Gateway API).
8 · ThingsBoard visualiza
ThingsBoard recibe el dato, lo guarda en su base de datos de series temporales y lo muestra en el dashboard en tiempo real — o dispara una alarma si supera un umbral.
De un Ubuntu recién instalado a los primeros datos en el dashboard.
Resumen de los pasos del stack; el docker-compose.yml completo, con nueve servicios (Postgres, Redis, Mosquitto, gateway-bridge, ChirpStack, Node-RED y ThingsBoard) y sus volúmenes, vive junto al resto de ficheros de configuración del proyecto.
Docker + Compose
Ubuntu Server con docker.io y el plugin docker-compose-plugin instalados.
Gateway LoRaWAN físico
RAK7248/7249, MikroTik wAP LR8/LR9, Dragino LPS8... con línea de vista razonable a los puntos de medición.
Un Dragino RS485-LN por bus
Un nodo por cada bus RS485 (Modbus permite varios SDM630 en el mismo bus, así que un cuadro con varios contadores puede compartir nodo).
Este script crea todo el árbol de directorios y archivos de configuración del stack —idéntico a los pasos 1 y 2 de abajo, con una contraseña de PostgreSQL y un secreto de API generados al azar— y lo levanta con docker compose up -d. Necesita Docker y docker-compose-plugin ya instalados (paso 0).
# dale permisos de ejecución y ejecútalo (crea ./energy-lora-stack por defecto) chmod +x bootstrap-energy-lora-stack.sh ./bootstrap-energy-lora-stack.sh [directorio-destino]
El script solo automatiza los pasos 1 y 2 (entorno + arranque del stack). Los pasos 3 a 7 (dar de alta el gateway físico, los contadores y el flujo de Node-RED) siguen siendo manuales: dependen de hardware y credenciales que solo tú tienes.
# copia la plantilla de variables de entorno cp .env.example .env nano .env # cambia CHIRPSTACK_PG_PASSWORD # la misma password debe reflejarse en: # configuration/chirpstack/chirpstack.toml -> [postgresql] dsn
Cambia también el secret de chirpstack.toml por una cadena aleatoria larga: openssl rand -base64 32.
docker compose up -d docker compose ps # comprueba que todo está "healthy"/"up" docker compose logs -f chirpstack # por si hay que depurar algo
3 · Gateway físico
Configura el packet forwarder Semtech UDP del gateway apuntando a la IP del servidor, puerto 1700. Luego dalo de alta en ChirpStack (Gateways → Add gateway) con su Gateway ID (EUI).
4 · Contadores en ChirpStack
Crea una Application, un Device Profile EU868/Clase A con el codec del SDM630 en la pestaña Codec, y da de alta cada contador como Device con su DevEUI/AppKey.
5 · Nodo Dragino RS485-LN
Configura el modo Modbus (baudrate, paridad) y los registros del SDM630 a leer, verificando el datasheet exacto de tu variante/firmware.
6 · Gateway en ThingsBoard
Crea un Device de tipo "Gateway", copia su Access Token y pégalo en el nodo broker-thingsboard del flujo de Node-RED.
7 · Importar el flujo en Node-RED
Abre el editor de Node-RED (panel de acceso, sección 03), importa node-red/flows-example.json, añade el Access Token de ThingsBoard en el nodo "ThingsBoard (Gateway API)" y despliega. Cada contador aparecerá como dispositivo hijo del gateway la primera vez que llegue una trama.
| Servicio | Puerto host | Credenciales por defecto |
|---|---|---|
| ChirpStack (web) | 8080/tcp | admin / admin |
| ThingsBoard (web) | 8081/tcp | sysadmin@thingsboard.org / sysadmin |
| Node-RED (editor) | 1880/tcp | sin autenticación por defecto |
| Mosquitto (MQTT) | 1883/tcp | allow_anonymous true por defecto |
| gateway-bridge (Semtech UDP) | 1700/udp | — (aquí apunta el gateway físico) |
Alcance de 5 km con LoRa: en EU868 con línea de vista razonable y el gateway en un punto alto, 5 km es alcanzable; en entorno urbano con obstáculos conviene una prueba de campo con un solo nodo antes de desplegar los 20-50 definitivos. Los intervalos de envío largos (varios minutos) permiten usar Spreading Factors altos (SF10-SF12): más alcance a costa de más tiempo en el aire, perfecto para supervisión energética sin necesidad de telemetría segundo a segundo.
De bytes crudos LoRaWAN a un JSON con voltajes, corrientes y potencia.
El Dragino RS485-LN concatena en el payload el resultado de cada AT+COMMAND configurado, en el mismo orden. ChirpStack ejecuta esta función decodeUplink (Device Profile → pestaña Codec) para convertir esos bytes en campos con nombre, antes de publicarlos en Mosquitto.
Formato de cada campo
8 registros float IEEE754 de 32 bits, big-endian ("ABCD"): V_L1, V_L2, V_L3, I_L1, I_L2, I_L3, P_total y Energía_total_kWh.
Cabecera del Dragino
Los 2 primeros bytes del payload suelen ser de estado/batería, no de datos Modbus — por eso el offset inicial es 2, no 0.
Ajusta antes de producción
La dirección de registro Modbus varía entre SDM630 / SDM630-MCT / SDM630M según firmware: verifica siempre el datasheet real de tu contador.
// bytes: array de 4 elementos, orden ABCD (big-endian), típico en SDM630 function bytesToFloat32(bytes) { var buffer = new ArrayBuffer(4); var view = new DataView(buffer); for (var i = 0; i < 4; i++) view.setUint8(i, bytes[i]); return view.getFloat32(0, false); // false = big-endian } function decodeUplink(input) { var bytes = input.bytes; var data = {}, errors = []; var offset = 2; // 2 bytes de cabecera (battery/status) del Dragino var campos = [ "voltaje_l1", "voltaje_l2", "voltaje_l3", "corriente_l1", "corriente_l2", "corriente_l3", "potencia_total_w", "energia_total_kwh" ]; try { for (var i = 0; i < campos.length; i++) { var chunk = bytes.slice(offset, offset + 4); if (chunk.length < 4) { errors.push("Payload corto en " + campos[i]); break; } data[campos[i]] = Math.round(bytesToFloat32(chunk) * 100) / 100; offset += 4; } } catch (e) { errors.push("Error decodificando: " + e.message); } return { data: data, errors: errors }; }
Antes de darlo por bueno: contrasta el orden exacto de bytes con la consola serie del Dragino (o su app de configuración) y con el repositorio oficial dragino-end-node-decoder en GitHub, que mantiene ejemplos actualizados por modelo.
Qué cambiar antes de pasar de laboratorio a producción.
La configuración de partida prioriza arrancar rápido para pruebas, no un despliegue expuesto a Internet. Antes de dar el stack por operativo con datos reales de clientes, repasa esto:
Mosquitto sin autenticación
Por defecto usa allow_anonymous true para simplificar el arranque. Crea usuarios con mosquitto_passwd y desactiva el acceso anónimo antes de producción.
Contraseñas y secretos por defecto
Cambia el admin/admin de ChirpStack, el sysadmin de ThingsBoard, el secret de la API de ChirpStack y la password de Postgres del .env.
Reverse proxy con TLS
Si el servidor es accesible desde Internet, pon Caddy o Traefik delante de los puertos web 8080/8081/1880 — nunca expongas esos puertos HTTP directamente.
Restringe el puerto del gateway
Limita 1700/udp (gateway-bridge) solo a las IPs de tus gateways físicos si tu red lo permite, para reducir la superficie expuesta.
¿Para quién y por quién se realiza esta oferta?
Estos datos aparecen en la portada y encabezados del informe (sección 11). Se guardan en este navegador y se comparten con el resto de herramientas del blog (localStorage), igual que en las demás calculadoras de Vatia&Co.
Estos mismos campos (con id que empieza por "p_") aparecen ya rellenos si los usaste antes en otra calculadora del blog, y viceversa.
Oferta económica: hardware, instalación y mantenimiento.
Partidas orientativas para un despliegue de este tipo (precios de referencia, editables uno a uno). Indica el número de contadores y de gateways y pulsa "Aplicar cantidades" para rellenar automáticamente las partidas que dependen de ellas — el resto de cantidades y precios se pueden ajustar libremente a mano.
Sugerencia de gateways: 1 por cada ~40 contadores en un radio de hasta 5 km con línea de vista razonable; ajusta según tu estudio de cobertura real.
Periodo de garantía: 36 meses de compromiso mínimo, ampliable hasta 120 meses (10 años) — orientado a la vida útil que suelen asignarse a las medidas de monitorización y control energético en el sistema de Certificados de Ahorro Energético (CAE), donde mantener la supervisión activa durante ese periodo es lo que permite acreditar el ahorro. Verifica la vida útil exacta en la ficha CAE vigente si el proyecto se va a presentar a certificación.
| Concepto | Cantidad | Precio unitario (€) | Subtotal | |
|---|---|---|---|---|
| Subtotal hardware | — | |||
| Concepto | Cantidad | Precio unitario (€) | Subtotal | |
|---|---|---|---|---|
| Subtotal instalación y puesta en marcha | — | |||
| Concepto | Cantidad | Precio unitario (€/mes) | Subtotal | |
|---|---|---|---|---|
| Subtotal mantenimiento mensual | — | |||
Durante el periodo de garantía (mínimo 36 meses, arriba) el mantenimiento es obligatorio y cubre actualizaciones del stack, copias de seguridad, soporte técnico y sustitución de hardware defectuoso — se contrata por todo el periodo, no mes a mes. Si el proyecto se acoge a un CAE, esta continuidad de la supervisión es además la que justifica el ahorro acreditado durante la vida útil de la medida.
Nota comercial: en este tipo de proyecto el hardware y la instalación suelen cotizarse muy ajustados frente a la competencia; el margen se concentra en esta cuota de mantenimiento, de coste relativamente alto y obligada durante toda la garantía — ajusta los precios de esta tabla en consecuencia antes de enviar la oferta.
Oferta económica generada a partir de tus datos.
Estructura orientativa: portada con datos del proyecto y cliente, alcance del stack, partidas de hardware/instalación/mantenimiento con sus importes, resumen económico y condiciones. Rellena las secciones 09 y 10 antes de generar el informe.
Pulsa "Generar / actualizar vista previa" para componer el documento con los datos introducidos, y "Descargar oferta en PDF" para obtener el informe descargable.